package service

import (
	"context"
	"gitee.com/crack007/goose"
	"gitee.com/crack007/goose/schedule"
	"github.com/crack007/go-im/pb"
	"github.com/crack007/go-im/src/common/model"
	"github.com/crack007/go-im/src/module/sentinel/job"
	"google.golang.org/grpc"
	"strconv"
)

type sentinelConnection struct {
	NodeConn map[string]pb.NodeServiceClient // node节点
}

func NewSentinelConnection() *sentinelConnection {
	return &sentinelConnection{
		NodeConn: make(map[string]pb.NodeServiceClient),
	}
}

// 和node节点创建连接
func (s *sentinelConnection) ConnectNode(nodeModel *model.NodeModel, sentinelModel *model.SentinelModel) error {
	if _, ok := s.NodeConn[nodeModel.Id]; ok {
		return nil
	}
	goose.GetLogger().Debug("ConnectNode-> id:%s ip:%s port:%d", nodeModel.Id, nodeModel.Ip, nodeModel.Port)
	target := nodeModel.Ip + ":" + strconv.Itoa(int(nodeModel.Port))
	conn, err := grpc.Dial(target, grpc.WithInsecure(), grpc.WithBlock())
	if err != nil {
		return err
	}
	client := pb.NewNodeServiceClient(conn)
	sentinelInfo := &pb.SentinelInfo{}
	sentinelInfo.Id = sentinelModel.Id
	sentinelInfo.Port = uint32(sentinelModel.Port)
	sentinelInfo.Ip = sentinelModel.Ip
	nodeMapInfo, err := client.OnConnectSentinel(context.Background(), sentinelInfo)
	if err != nil {
		return err
	}
	for _, sentinel := range nodeMapInfo.SentinelList {
		err := s.ConnectSentinel(model.GetSentinelModel(sentinel), SentinelService.CurrentNode)
		if err != nil {
			return err
		}
	}
	s.NodeConn[nodeModel.Id] = client
	// 投递一个定时任务
	schedule.SubmitJob(job.NewNodePingJob(client, sentinelModel, nodeModel, func(target *model.NodeModel) {
		s.NodeOfflineAsk(target.Id)
	}))
	SentinelService.AddNode(nodeModel)
	return nil
}

// 和Sentinel主动连接
func (s *sentinelConnection) ConnectSentinel(targetSentinel *model.SentinelModel, sourceSentinel *model.SentinelModel) error {
	if targetSentinel.Id == sourceSentinel.Id {
		return nil
	}
	if v, ok := SentinelService.workNodeClusterModel.SentinelMap[targetSentinel.Id]; ok {
		if v.Client != nil {
			return nil
		}
	}

	goose.GetLogger().Debug("ConnectSentinel-> id:%s ip:%s port:%d", targetSentinel.Id, targetSentinel.Ip, targetSentinel.Port)
	target := targetSentinel.Ip + ":" + strconv.Itoa(int(targetSentinel.Port))
	conn, err := grpc.Dial(target, grpc.WithInsecure(), grpc.WithBlock())
	if err != nil {
		return err
	}
	client := pb.NewSentinelServiceClient(conn)
	targetSentinel.Client = client
	SentinelService.workNodeClusterModel.SentinelMap[targetSentinel.Id] = targetSentinel
	sentinelInfo := model.GetSentinelInfo(sourceSentinel)

	sentinelModel := model.NewSentinelModel()
	sentinelModel.Id = targetSentinel.Id
	sentinelModel.Ip = targetSentinel.Ip
	sentinelModel.Port = targetSentinel.Port
	sentinelModel.Client = client
	SentinelService.AddSentinelNode(sentinelModel)

	SentinelService.CurrentNode.Id = sourceSentinel.Id
	SentinelService.CurrentNode.Ip = sourceSentinel.Ip
	SentinelService.CurrentNode.Port = sourceSentinel.Port
	// 投递一个定时任务
	schedule.SubmitJob(job.NewSentinelPingJob(client, targetSentinel, sourceSentinel, func(targetSentinel *model.SentinelModel) {
		s.SentinelOfflineAsk(targetSentinel)
	}))
	s.Broadcast(func(sentinelModel *model.SentinelModel) {
		sentinelModel.Client.SentinelOnline(context.Background(), sentinelInfo)
	})
	return nil
}

func (s *sentinelConnection) SentinelOfflineAsk(offlineModel *model.SentinelModel) {
	offline := 1
	sentinelInfo := model.GetSentinelInfo(offlineModel)
	SentinelConnection.Broadcast(func(sentinelModel *model.SentinelModel) {
		if offlineModel.Id == sentinelModel.Id {
			return
		}
		nodeStatus, err := sentinelModel.Client.SentinelStatusAsk(context.Background(), sentinelInfo)
		if err != nil {
			return
		}
		if !nodeStatus.Online {
			offline++
		}
	})
	total := SentinelService.GetSentinelCount() - 1
	goose.GetLogger().Debug("判断结果-> offline:%d total:%d", offline, total)
	if offline > (total/2)+1 {
		goose.GetLogger().Debug("标记下线-> id:%s", offlineModel.Id)
		// 标记为下线
		s.RemoveSentinel(offlineModel)
	}
}
func (s *sentinelConnection) NodeOfflineAsk(id string) {
	var nodeModel *model.NodeModel
	if _, ok := SentinelService.workNodeClusterModel.NodeMap[id]; !ok {
		return
	}
	nodeModel = SentinelService.workNodeClusterModel.NodeMap[id]
	masterInfo := &pb.NodeInfo{}
	masterInfo.Id = id
	offline := 1
	s.Broadcast(func(sentinelModel *model.SentinelModel) {
		nodeStatus, err := sentinelModel.Client.NodeStatusAsk(context.Background(), masterInfo)
		if err != nil {
			return
		}
		if !nodeStatus.Online {
			offline++
		}
	})

	total := SentinelService.GetSentinelCount()
	goose.GetLogger().Debug("判断结果-> offline:%d total:%d", offline, total)
	if offline > (total/2)+1 {
		SentinelService.SetNodeOffline(id)
		// TODO 广播节点下线
		if nodeModel.IsMaster {
			SentinelService.Vote(nodeModel)
		}
	}
}

func (s *sentinelConnection) TestNode(id string) bool {
	if _, ok := s.NodeConn[id]; !ok {
		return false
	}
	_, err := s.NodeConn[id].OnPing(context.Background(), &pb.PingReq{})
	return err == nil
}

func (s *sentinelConnection) TestSentinel(id string) bool {
	if _, ok := SentinelService.workNodeClusterModel.SentinelMap[id]; !ok {
		return false
	}
	_, err := SentinelService.workNodeClusterModel.SentinelMap[id].Client.Ping(context.Background(), &pb.PingReq{})
	return err == nil
}
func (s *sentinelConnection) Broadcast(callback func(sentinelModel *model.SentinelModel)) {
	for _, sentinelModel := range SentinelService.workNodeClusterModel.SentinelMap {
		if sentinelModel.Id == SentinelService.CurrentNode.Id {
			continue
		}
		callback(sentinelModel)
	}
}
func (s *sentinelConnection) RemoveSentinel(sentinelModel *model.SentinelModel) {
	delete(SentinelService.workNodeClusterModel.SentinelMap, sentinelModel.Id)
}
